"""
firings.py

Thu Oct 11 11:12:18 CEST 2012

This file contains functions to deal with firings of neurons
as described in Gibson & Robinson, 1992 (that means 0, no firing and
1 means firing).

Pattern: manage a set of random patterns to apply to a network. Patterns
are constructed based on a constant probability (a) of neurons to be active.
This is the probability that the i-th neuron was active in an original pattern.

Firing: simulates the spontaneous firing of neurons in a network.

Requires NumPy 1.6 or above
"""

import numpy as np

class Pattern(np.ndarray):
    """
    generates a pattern object of active and inactive neurons. This is a
    vector of size n (number of neurons) containing 1 if the neuron 
    is active, zero othewise. 
    To assign the consider that every i-th element in the vector is 
    an independent random that takes the value 1 with constant probability 

    $\Pr(Z^p=1) = a$
    $\Pr(Z^p=0) = 1-a$
    
    a is the  probability that the i-th neuron in the pattern 
    belongs to the initial pattern
    
        n   -- number of cells
        a   -- activity (i.e probability that a neuron belongs 
               to the original pattern.

    Returns:
    a NumPy array 

    Example:
    >>> from firings import pattern
    >>> Z = pattern(n = 10, a = 0.5)
    >>> Z # check the initial pattern
    >>> array([1,1,1,1,1,0,0,0,0,0]
    >>> to check the i-th neuron in the p-th pattern
    >>> Z[p][i]
    """
    def __new__(subtype, n, a):
        nactive = int(n*a) # number of active neurons

        obj = np.ndarray.__new__(subtype, shape = (1,n), dtype=int,\
            buffer=None, offset=0, strides=None, order=None)        
        obj.fill(0)
        obj[0][:nactive] = 1

        obj.a = a
        obj.n = n
        obj.nactive = nactive
        return obj 

    def __array_finalize__(self, obj):
        if obj is None: 
            return
        self.a = getattr(obj, 'a', None)
        self.n = getattr(obj, 'n', None)
        self.nactive = getattr(obj, 'nactive', None)

def generate(pattern_obj, m = None):
    """
    generate m patterns with a constant probability of 
    neurons being active (a)

    pattern_obj --  the initial pattern, this is a Pattern object 
    m           --  number of patterns to generate. If None, then 
                    a single new pattern will be generated in the object
        

    To generate 10 patterns
    >>> Z = Pattern(n=10, a=0.2) # Z is the original pattern
    >>> Y = generate(Z, m=3) # generate 3 patterns based on Z 
    >>> To see how many patterns we have in this object type:
    >>> Y.shape[0] 
    """
    
    myarray = pattern_obj
    for _ in range(m):
        pattern = (np.random.rand(pattern_obj.n) > pattern_obj.a).choose(1,0)
        myarray = np.vstack( (myarray, pattern) )
    
    return(myarray)

def get_valid(pattern_obj, nparray):
    """
    return the number of valid firings when compared with an
    original pattern

    pattern_obj --  the initial pattern, this is a Pattern object 
    
    """

    # count the number of ones in the first nactive elements
    return (np.count_nonzero(nparray[:pattern_obj.nactive]) )

def get_spurious(pattern_obj, nparray):
    """
    return the number of valid firings when compared with an
    original pattern

    pattern_obj --  the initial pattern, this is a Pattern object 
    
    """

    # count the number of ones in the first nactive elements
    return( np.count_nonzero(nparray[pattern_obj.nactive:]) )

def get_overlap(pattern_obj, nparray):
    """
    Get the similarity between the pattern object and 
    the vector of activities.
    Similarity is calculated by the normalized inner product
    between these 2 vectors
    """
    from numpy.linalg import norm

    X = pattern_obj
    Y = nparray
    if norm(X)==0 or norm(Y) == 0:
        return(0.0)
    else:
        return ( np.inner( X/norm(X), Y/norm(Y) ) )
